#include "Util.hpp"
#include "Trace.hpp"
#include "UnitTest.hpp"
#include "TaggedString.hpp"

#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <cstring>
using std::strlen;
#include <string>
#include <cstdarg>
#include <set>
using std::set;
#include <map>
using std::map;
using std::pair;
#include <algorithm>
#include <fstream>

MessageException::MessageException(string message)
	{ this->message = message; }
string MessageException::toString()
	{ return message; }

vector<char> tagString(string cmd)
{
	vector<char> ret;
	char quote = 0;
	bool escapeNext = false;
	
	for(size_t ii=0; ii<cmd.length(); ii++)
	{
		if(quote)
		{
			if(cmd[ii]==quote && !escapeNext) {
				quote = 0;
				ret.push_back(tagUnquoted);
			} else if(quote=='\'') {
				ret.push_back(tagSingleQuoted);
			} else if(quote=='\"') {
				ret.push_back(tagDoubleQuoted);
			}
		}
		else
		{
			ret.push_back(tagUnquoted);
			if(!escapeNext && (cmd[ii]=='\'' || cmd[ii]=='\"'))
				quote = cmd[ii];
		}
		
		if(escapeNext) {
			ret[ret.size()-1] |= tagEscaped;
			escapeNext = false;
		}
		else if(cmd[ii]=='\\')
			escapeNext = true;
	}
	
	return ret;
}

bool isPrefix(string A, string B)
{
	if(A.length() > B.length())
		return false;
	
	for(unsigned ii=0; ii<A.length(); ii++)
	{
		if(A[ii] != B[ii])
			return false;
	}
	
	return true;
}

void randomFill(char *buf, int len)
{
	for(int ii=0; ii<len; ii++)
		buf[ii] = (char)randInt(0, 255);
}

int randInt(int low, int high)
{
	return rand()%(high-low+1) + low;
}

char *loadCompleteFile(const char *filename)
{
	FILE *fin = fopen(filename, "rb");
	if(!fin)
		return NULL;
	
	size_t alloc = 2048;
	size_t len = 0;
	char *buf = (char*)malloc(alloc);
	
	for(;;)
	{
		size_t bytesRead = fread(buf, 1, alloc-len-1, fin);
		if(bytesRead > 0) {
			len += bytesRead;
			if(len+1>=alloc) {
				alloc *= 2;
				buf = (char*)realloc(buf, alloc);
			}
		} else {
			break;
		}
	}
	
	buf[len] = 0;
	
	fclose(fin);
	return buf;
}

bool stringIsInt(const string &str)
{
	if(!str.length())
		return false;
	
	unsigned pos = 0;
	if(str[0]=='-')
		pos++;
	
	for(; pos<str.length(); pos++)
	{
		if(!isdigit(str[pos]))
			return false;
	}
	return true;
}

bool isHexDigit(char ch)
{
	if(ch>='0' && ch<='9')
		return true;
	else if(ch>='a' && ch<='f')
		return true;
	else if(ch>='A' && ch<='F')
		return true;
	else
		return false;
}

char intToHexDigit(int n)
{
	if(n>=0 && n<=9)
		return n+'0';
	else if(n>=10 && n<=15)
		return n-10+'A';
	else
		return 0;
}

int hexDigitToInt(char ch)
{
	if(ch>='0' && ch<='9')
		return ch-'0';
	else if(ch>='a' && ch<='f')
		return ch+10-'a';
	else if(ch>='A' && ch<='F')
		return ch+10-'A';
	else
		return -1;
}

int skipWhitespace(string str, int pos)
{
	while((unsigned)pos<str.length() && isspace(str[pos]))
		pos++;
	return pos;
}

string backslashReduce(const TaggedString &str)
{
	string ret = "";
	const char *start = str.c_str();
	for(const char *pos=start; *pos; )
	{
		if(*pos == '\\')
		{
			ret += parseEscapeSequence(pos);
		}
		else
		{
			ret += *pos;
			pos++;
		}
	}
	return ret;
}

/// Separate the given string into arguments based on unquoted separators.
/// If keepSeparators is true, then separators form their own tokens, so that
/// the strings in args concatenated together are exactly the original string.
/// Otherwise, unquoted/unescaped separator characters are discarded.
void separateArgs(vector<string> &args, const char *str, bool keepSeparators, const char *separators)
{
	args.clear();
	
	if(!str || !*str)
		return;
	
	TaggedString taggedStr(str);
	
	int argStart = 0;
	int len = taggedStr.length();
	bool inSeparator = strchr(separators, taggedStr[0]);
	for(int ii=0; ii<len; ii++)
	{
		if(taggedStr.isQuoted(ii) || taggedStr.isEscaped(ii))
			continue;
		
		char ch = taggedStr[ii];
		bool isSeparator = strchr(separators, ch);
		
		if(isSeparator != inSeparator)
		{
			if(!inSeparator || keepSeparators)
				args.push_back(taggedStr.substr(argStart, ii-argStart));
			
			argStart = ii;
			inSeparator = isSeparator;
		}
	}
	
	if(len>argStart)
	{
		if(!inSeparator || keepSeparators)
			args.push_back(taggedStr.substr(argStart, len-argStart));
	}
}

void separateLines(vector<string> &lines, const char *str)
{
	unsigned start = 0;
	unsigned ii;
	
	lines.clear();
	
	for(ii=0; str[ii]; ii++)
	{
		if(str[ii]=='\n') {
			lines.push_back(string(str+start, ii-start));
			start = ii+1;
		}
	}
	
	if(ii > start)
		lines.push_back(string(str+start, ii-start));
}

string combineArgs(const vector<string> &args)
{
	string ret="";
	
	for(unsigned ii=0; ii<args.size(); ii++)
	{
		if(ii) ret += " ";
		ret += quoteProtectString(args[ii]);
	}
	return ret;
}

string stringListToString(const list<string> &strs)
{
	string ret = "";
	bool first = true;
	for(list<string>::const_iterator ii=strs.begin(); ii!=strs.end(); ii++)
	{
		if(first)
			first = false;
		else
			ret += " ";
		ret += quoteProtectString(*ii);
	}
	return ret;
}

list<string> stringToStringList(string str)
{
	vector<string> strs;
	separateArgs(strs, str.c_str());
	
	for(unsigned ii=0; ii<strs.size(); ii++)
		strs[ii] = unquoteString(strs[ii].c_str());
	list<string> ret;
	ret.insert(ret.end(), strs.begin(), strs.end());
	return ret;
}

TEST_SET(stringList)
{
	list<string> test;
	test.push_back("");
	test.push_back("abc");
	test.push_back("abc def");
	
	string str = stringListToString(test);
	testAssert(test == stringToStringList(str));
}

string timeToString(time_t secsSinceEpoch)
{
	struct tm *time = gmtime(&secsSinceEpoch);
	char buf[64];
	sprintf(buf, "%i-%02i-%02i %02i:%02i", 1900+time->tm_year, time->tm_mon, time->tm_mday, time->tm_hour, time->tm_min);
	return buf;
}


string quoteProtectString(string str)
{
	if(!str.length())
		return "\"\"";
	
	bool needsQuotes = false;
	
	string ret="";
	
	for(unsigned ii=0; ii<str.length(); ii++)
	{
		char ch = str[ii];
		if(ch<32 || ch>=127) {
			// Control character or fragment of non-ASCII UTF-8
			ret += getEscapeSequence(ch);
			needsQuotes = true;
		} else if(ch=='\\') {
			ret += "\\\\";
			needsQuotes = true;
		} else if(ch=='\"' || ch=='\'' || ch=='`') {
			ret += string("\\")+ch;
			needsQuotes = true;
		} else {
			// Printable ASCII character
			ret += ch;
		}
		
		if(isspace(ch))
			needsQuotes = true;
	}
	
	if(needsQuotes)
		return string("\"")+ret+"\"";
	else
		return ret;
}

string unquoteString(const char *str)
{
	int pos = 0;
	string ret = "";
	int leadQuote = str[pos++];
	
	// If not quoted, only reduce escape sequences
	if(leadQuote != '\"' && leadQuote != '\'' && leadQuote != '`')
		return str;
	
	while(str[pos] && str[pos]!=leadQuote)
	{
		if(str[pos]=='\\') {
			const char *escapePos=str+pos;
			ret += parseEscapeSequence(escapePos);
			pos = escapePos-str;
		} else {
			ret += str[pos++];
		}
	}
	return ret;
}

string getEscapeSequence(char ch)
{
	switch(ch)
	{
		case '\a': return "\\a";
		case '\b': return "\\b";
		case '\f': return "\\f";
		case '\n': return "\\n";
		case '\r': return "\\r";
		case '\t': return "\\t";
		case '\v': return "\\v";
		case '\'': return "\\'";
		case '\"': return "\\\"";
		case '\\': return "\\\\";
		
		default: {
			char ret[5];
			ret[0] = '\\';
			ret[1] = 'x';
			ret[2] = intToHexDigit((unsigned char)ch >> 4);
			ret[3] = intToHexDigit(ch&0x0F);
			ret[4] = 0;
			return string(ret);
		}
	}
}

string parseEscapeSequence(const char *&start)
{
	Assert(*start == '\\');
	start++;
	
	switch(*start)
	{
		case 0: return "";
		
		// Proper single-character escape sequences
		case 'a':  start++; return "\a";
		case 'b':  start++; return "\b";
		case 'f':  start++; return "\f";
		case 'n':  start++; return "\n";
		case 'r':  start++; return "\r";
		case 't':  start++; return "\t";
		case 'v':  start++; return "\v";
		case '\'': start++; return "\'";
		case '\"': start++; return "\"";
		case '\\': start++; return "\\";
		case '?':  start++; return "?'";
		
		// Escaped whitespace
		case ' ':  start++; return " ";
		case '\t':  start++; return "\t";
		case '\n':  start++; return "\n";
		
		// Hexadecimal number
		case 'x':
			start++;
			if(!isHexDigit(start[0])) {
				return "";
			} else if(!isHexDigit(start[1])) {
				char ret[2] = { hexDigitToInt(start[0]), 0 };
				start++;
				return string(ret,1);
			} else {
				char ret[2] = { (hexDigitToInt(start[0])<<4) + hexDigitToInt(start[1]), 0 };
				start+=2;
				return string(ret,1);
			}
		
		// Octal number
		// A leading zero followed by other digits is ambiguous; it can either
		// be a null character followed by those digits as chars, or it can be
		// the char denoted by those digits.
		case 'o':
			start++;
			//FALLTHROUGH
		case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
		{
			int ret = *start-'0';
			start++;
			if(*start>='0' && *start<='7')
			{
				ret <<= 3;
				ret += *start-'0';
				start++;
				if(*start>='0' && *start<='7') {
					ret <<= 3;
					ret += *start-'0';
					start++;
				}
			}
			char str[2] = {ret,0};
			return string(str,1);
		}
		
		// For anything else, just take the escaped character as-is
		default: {
			string ret = "";
			ret += *start;
			start++;
			return ret;
		}
	}
}

string extractExtension(string filename)
{
	for(int ii=filename.length()-1; ii>=0; ii--)
	{
		if(filename[ii]=='.') {
			return filename.substr(ii, filename.length()-ii);
		} else if(filename[ii]=='/') {
			break;
		}
	}
	
	return "";
}

TEST_SET(extractExtension)
{
	testAssert(extractExtension("foo.txt")==".txt");
	testAssert(extractExtension("foo")=="");
	testAssert(extractExtension("f.o.o/bar")=="");
	testAssert(extractExtension("foo.txt.gz")==".gz");
	testAssert(extractExtension("foo.txt.gz.")==".");
}

TEST_SET(separateArgs)
{
	vector<string> args;
	
	separateArgs(args, "");
	testAssert(args.size()==0);
	if(args.size()!=0) return;
	
	separateArgs(args, "\r\n");
	testAssert(args.size()==0);
	
	separateArgs(args, " \t ");
	testAssert(args.size()==0);
	if(args.size()!=0) return;
	
	separateArgs(args, " abc ");
	testAssert(args.size()==1);
	if(args.size()!=1) return;
	testAssert(args[0]=="abc");
	
	separateArgs(args, "\" abc \"");
	testAssert(args.size()==1);
	if(args.size()!=1) return;
	testAssert(args[0]=="\" abc \"");
	
	separateArgs(args, "a  b ", true);
	testAssert(args.size()==4);
	if(args.size()!=4) return;
	testAssert(args[0]=="a");
	testAssert(args[1]=="  ");
	testAssert(args[2]=="b");
	testAssert(args[3]==" ");
	
	separateArgs(args, "a\\ b");
	testAssert(args.size()==1);
}

TEST_SET(separateLines)
{
	vector<string> lines;
	
	separateLines(lines, "");
	testAssert(lines.size()==0);
	if(lines.size()!=0) return;
	
	separateLines(lines, "\n");
	testAssert(lines.size()==1);
	if(lines.size()!=1) return;
	testAssert(lines[0] == "");
	
	separateLines(lines, "abc\n\ndef");
	testAssert(lines.size()==3);
	if(lines.size()!=3) return;
	testAssert(lines[0] == "abc");
	testAssert(lines[1] == "");
	testAssert(lines[2] == "def");
}
TEST_SET(combineArgs)
{
	vector<string> args;
	
	testAssert( combineArgs(args)=="" );
	
	args.push_back("abc");
	testAssert( combineArgs(args)=="abc" );
	
	args.push_back("d e f");
	testAssert( combineArgs(args)=="abc \"d e f\"" );
	
	string argWithZero("x");
	argWithZero[0] = 0;
	args.push_back(argWithZero);
	testAssert( combineArgs(args)=="abc \"d e f\" \"\\x00\"" );
}
TEST_SET(quoteProtectString)
{
	string strWithNull("x\n\b");
	strWithNull[0] = 0;
	
	testAssert(quoteProtectString("") == "\"\"");
	testAssert(quoteProtectString("abc") == "abc");
	testAssert(quoteProtectString("a b c") == "\"a b c\"");
	testAssert(quoteProtectString("\\") == "\"\\\\\"");
	testAssert(quoteProtectString(strWithNull) == "\"\\x00\\n\\b\"");
}
TEST_SET(unquoteString)
{
	testAssert(unquoteString("\"abc def\"")=="abc def");
	testAssert(unquoteString("\'abc def\'")=="abc def");
	testAssert(unquoteString("abc def")=="abc def");
	testAssert(unquoteString("\"\\\"abc def\\\"\"")=="\"abc def\"");
	
	// Test that quoteProtectString and unquoteString are proper inverse
	const char *testStrings[] = {
		"",
		"\\",
		"\\\\",
		"\"\'\"",
		" \" ",
		"a `b`",
		NULL
	};
	for(const char **pos = testStrings; *pos; pos++)
	{
		const char *testString = *pos;
		testAssert(unquoteString(quoteProtectString(testString).c_str()) == testString);
	}
}
TEST_SET(getEscapeSequence)
{
	testAssert(getEscapeSequence('\n')=="\\n");
	testAssert(getEscapeSequence('\xFF')=="\\xFF");
}
TEST_SET(parseEscapeSequence)
{
	const char *str;
	const char *pos;
	
	str = pos = "\\n";
	testAssert(parseEscapeSequence(pos)=="\n");
	testAssert(pos==str+2);
	
	str = pos = "\\";
	testAssert(parseEscapeSequence(pos)=="");
	testAssert(pos==str+1);
	
	str = pos = "\\xFf";
	testAssert(parseEscapeSequence(pos)=="\xFF");
	testAssert(pos==str+4);
	
	str = pos = "\\1";
	testAssert(parseEscapeSequence(pos)=="\1");
	testAssert(pos==str+2);
	
	str = pos = "\\12";
	testAssert(parseEscapeSequence(pos)=="\12");
	testAssert(pos==str+3);
	
	str = pos = "\\123";
	testAssert(parseEscapeSequence(pos)=="\123");
	testAssert(pos==str+4);
	
	str = pos = "\\o123";
	testAssert(parseEscapeSequence(pos)=="\123");
	testAssert(pos==str+5);
}

std::string retvprintf(const char *fmt, va_list args)
{
	char buf[4096];
	vsnprintf(buf, 4096, fmt, args);
	return buf;
}

string retprintf(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	string ret = retvprintf(fmt, args);
	va_end(args);
	return ret;
}

